home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PsL Monthly 1993 December
/
PSL Monthly Shareware CD-ROM (December 1993).iso
/
prgmming
/
dos
/
c
/
c_hints.exe
/
HINTS.DOC
< prev
Wrap
Text File
|
1992-07-12
|
32KB
|
759 lines
HINTS FOR EFFICIENT PROGRAMMING
Copyright (c) 1992 by Omega Point, Inc.
Author: Ratko V. Tomic
-------------------------------------------------------------------------
NOTE: The text below is taken from the CodeRunneR (R) manual. CodeRunneR
is a TSR library for C and assembly programmers, thus many hints
deal implicitly with size optimization. For more information about
CodeRunneR call Omega Point (508) 877-1819 or fax (508) 877-0915.
-------------------------------------------------------------------------
CodeRunneR was designed with the greatest attention for efficiency (size
and speed), all functions were carefully crafted in assembly language.
Combined with some care in the C code you can produce programs rivaling
in size to average assembly language programs, especially on medium and
large projects. On very small programs (few hundred bytes), the assembly
language will still be ahead. In terms of speed, the video functions
(at least) should be competitive with pure assembly code.
The C programming hints below have been found useful in reducing the
efficiency gap between C and assembly code.
1. INT or CHAR - Current compilers are notoriously bad in handling
expressions involving byte size data. The reason for this is an
overly literal interpretation of C "abstract machine", which mandates
that the result should be evaluated as if extended to integer. On the
other hand, the xxx86 processors have a rich subset of instructions
operating on byte size items. These instructions make great majority
of "integral promotions" unnecessary. Unfortunately, only a small
fraction of these are utilized by C compilers, resulting in much more
expensive operations on char versus int variables. Thus a saving of
one byte for the variable storage is practically always more than
paid for in code size.
The following rules have been found useful in reducing this type
of inefficiency:
a) If a char variable is used within expressions, or is being passed to
functions, or returned by functions, it is better to use int instead.
This is true even in case when variable is only used as TRUE/FALSE
flag. If defined as an int, the variable will occupy 1 more byte,
but for each use one or more bytes are saved in generated code.
In addition the speed is often improved due to an even address
alignment of variables, preferred by the 16/32 bit CPU-s.
b) In some situation rule (a) cannot be applied, since an array of
characters is the source for byte size variables. In that case
the characters should be cast as SIGNED whenever possible since
the instruction CBW used to extend signed char to int is 1 byte,
while the instruction MOV AH,0 used for unsigned is 2 bytes.
c) CodeRunner functions which receive char or byte as an argument
will ignore high byte passed. Hence you can safely pass integers
without having to mask out high byte. This also allows application
of the rule (b) independently of char being below or above 0x80.
d) Auto variables of char type will occupy two bytes on stack
anyways, hence one should always gain by using int instead.
e) If an integer variable is to be compared to a char, or passed to
function expecting char, casting integer to char is better than
AND-ing it with 0xff.
2. INITIALIZED AUTO variables for strings, arrays, structures or any
composite objects are very inefficient. Each such object will
occupy exactly twice the size defined. Also, the compiler will
on every function entry call an internal transfer function to
copy the variable from original place into the auto variable.
Besides wasting time, this transfer function is not usable by
the C code as a memory copy function. And lastly, the space
used as the destination is also internal space (stack space),
which will often place much heavier demand on stack size. You
can often create a "local copy" of such item in some temporarily
free buffer (e.g. a disk i/o buffer when disk i/o is idle).
a) If this type of variables is truly needed (rare case), one may
use other general purpose memory copy function (like memcpy())
which is usable for other purposes as well. In case of Turbo C
this is even more important, since Turbo C uses far call and
32 bit pointers to perform the copying, even in small model.
b) In most situations, the intention was only to keep a variable
local to the function and not to reinitialize variable each time
function is activated. In that case the variable should be turned
into static, accomplishing same result in half the size.
Hence instead of:
func()
{
char msg[]="This is an initialized local string.";
}
it is better to use:
func()
{
static char msg[]="This is an initialized local string.";
}
This is one example where ANSI C standard has violated a useful
classic C rule that more efficient constructs are shorter to type.
3. Another case of the same Classic C rule violation is the passing of
structures by value to/from functions. Such deceivingly efficient
constructs are wasteful on memory and time even when implemented
well, and especially in the current C compilers where they were added
as a marketing feature to boost the count of ANSI compliance points.
Hence instead of:
struct x_type func(struct x_type x)
{
....
process(x.field);
....
return(x);
....
}
it is more efficient to use:
void funct(struct x_type *x)
{
...
process(x->field);
...
return;
}
In rare circumstances, where a local copy of a structure is needed in
order to be modified while preserving the callers copy, it is still
better to use a general purpose memory copy, rather than have compiler
link in an internal-use-only transfer function.
4. Uninitialized AUTO variables are more efficient than the equivalent
static/global variables both in memory (typically 1 byte per access)
and speed. Additionally, the space used by auto variables is released
when function exits. Therefore both simple and composite objects
needed temporarily and locally should be defined as auto variables.
Similar situation is with arguments a function receives - they are
equivalent to auto variables in efficiency, thus they are often
better than global variables.
5. Variables which are OFTEN being passed as arguments between functions
should be redefined as globals, ar at least static for that module.
This rule needs to be counterbalanced with the previous rule - the
decision parameter is "often". Namely each access to a global/static
costs on average 1 extra byte (compared to access of a function
argument), but the cost of passing such variable is on average 5 bytes.
6. If a variable occurs in the code as say: n-1, two or more times, it
is better to define another variable, n1=n-1 and use n1 instead.
Just the code space saved on the single re-use pays for the
extra space of variable n1. The program speed will benefit too.
This rule is applicable to char, int, long variables as well as
pointers to any objects. More complex expressions (than n-1)
will benefit even more.
Note also that the assignement (n1=n-1; above) should be combined
with the first use of (n-1):
For example instead of:
y = f(x) + n - 1;
z = v * (n-1);
use:
y = f(x) + (n1=n-1);
z = v * n1;
7. 2-D arrays are not efficiently handled by C compilers. Each access
will produce multiply instruction to compute an offset into the array.
If a 2-D array is used mostly in a sequential manner (like screens,
edit buffers, matrices, etc.) one should define them as 1-D arrays
and compute starting offset expli